package cn.tedu.lbs.basic.web.service;

import cn.tedu.lbs.basic.web.manager.BasicOuputSourceManager;
import cn.tedu.lbs.basic.web.repository.AdminRepository;
import cn.tedu.lbs.basic.web.repository.StoreESRepository;
import cn.tedu.lbs.basic.web.repository.StoreRepository;
import cn.tedu.lbs.basic.web.util.AuthenticationContextUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.Query;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tarena.lbs.base.common.utils.Asserts;
import com.tarena.lbs.base.protocol.exception.BusinessException;
import com.tarena.lbs.base.protocol.pager.PageResult;
import com.tarena.lbs.common.passport.enums.Roles;
import com.tarena.lbs.common.passport.principle.UserPrinciple;
import com.tarena.lbs.pojo.basic.entity.StoreSearchEntity;
import com.tarena.lbs.pojo.basic.event.LocationEvent;
import com.tarena.lbs.pojo.basic.param.StoreParam;
import com.tarena.lbs.pojo.basic.param.UserLocationParam;
import com.tarena.lbs.pojo.basic.po.AdminPO;
import com.tarena.lbs.pojo.basic.po.StorePO;
import com.tarena.lbs.pojo.basic.query.AdminQuery;
import com.tarena.lbs.pojo.basic.query.AreaStoreQuery;
import com.tarena.lbs.pojo.basic.query.StoreQuery;
import com.tarena.lbs.pojo.basic.vo.AdminVO;
import com.tarena.lbs.pojo.basic.vo.StoreVO;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
public class StoreService {
    @Autowired
    private StoreRepository storeRepository;
    @Autowired
    private AdminRepository adminRepository;
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    public PageResult<StoreVO> pageList(StoreQuery query) throws BusinessException {
        Integer businessId=checkAndGetBusinessId();
        PageResult<StoreVO> voPage=new PageResult<>(10,1);
        voPage.setTotal(100L);
        //查询当前平台所有店铺
        List<StorePO> pos =null;
        if (businessId!=null){
            QueryWrapper<StorePO> queryWrapper=new QueryWrapper<>();
            queryWrapper.eq("business_id",businessId);
            //select * from lbs_store where business_id=?
            pos=storeRepository.list(queryWrapper);
        }else{
            pos=storeRepository.list();
        }
        //商家店铺 可能查询结果是空
        if(pos!=null&&pos.size()>0) {
            voPage.setObjects(pos.stream().map(po -> {
                StoreVO vo = new StoreVO();
                BeanUtils.copyProperties(po, vo);
                return vo;
            }).collect(Collectors.toList()));
        }
        return voPage;
    }

    private Integer checkAndGetBusinessId() throws BusinessException {
        //1.拿到认证对象
        UserPrinciple userPrinciple = AuthenticationContextUtil.get();
        //2.断言空
        Asserts.isTrue(userPrinciple==null,new BusinessException("-2","用户认证解析失败"));
        //3.拿到角色
        Roles role = userPrinciple.getRole();
        Integer businessId=null;
        //4.如果角色是SHOP 需要给businessId赋值
        if (role==Roles.SHOP){
            AdminPO po = adminRepository.getById(userPrinciple.getId());
            Asserts.isTrue
                    (po==null,new BusinessException("-2","登录用户根本不存在"));
            businessId=po.getBusinessId();
        }
        return businessId;
    }

    public void save(StoreParam storeParam) throws BusinessException {
        Integer businessId = checkAndGetBusinessId();
        //如果businessId是空的 说明角色不正确
        Asserts.isTrue
                (businessId==null,new BusinessException("-2","店铺无法绑定商家"));
        //检查businessId是否真的有商家 UNDO
        //新增到 数据层 mysql es
        Integer id=saveStore(storeParam);
        //bindPicture UNDO
    }
    @Autowired
    private StoreESRepository storeESRepository;
    private Integer saveStore(StoreParam storeParam) {
        //1.新增到数据库
        StorePO poParam=new StorePO();
        BeanUtils.copyProperties(storeParam,poParam);
        //1.1 入参地理位置
        poParam.setStoreLongitude(storeParam.getLongitude());
        poParam.setStoreLatitude(storeParam.getLatitude());
        //1.2 时间 create update
        poParam.setCreateTime(new Date());
        poParam.setUpdateTime(new Date());
        storeRepository.save(poParam);
        //2.新增到ES
        StoreSearchEntity entity=new StoreSearchEntity();
        entity.setId(poParam.getId());
        entity.setBusinessId(poParam.getBusinessId());
        entity.setStoreName(poParam.getStoreName());
        //纬度,经度 location=39.909999,116.0428998
        entity.setLocation(poParam.getStoreLatitude()+","+poParam.getStoreLongitude());
        storeESRepository.save(entity);
        return poParam.getId();
    }

    public PageResult<StoreVO> getAreaStores(AreaStoreQuery areaStoreQuery) throws BusinessException {
        //1.检查认证结果 获取商家账号对应商家id
        Integer businessId = checkAndGetBusinessId();
        //2.组织sql的where条件 查询满足区域 满足businessId的店铺列表
        List<StorePO> pos=getAreaStores(areaStoreQuery,businessId);
        //3.封装分页对象
        return assemblePageResult(pos);
    }

    private PageResult<StoreVO> assemblePageResult(List<StorePO> pos) {
        PageResult<StoreVO> voPage=new PageResult<>(10,1);
        voPage.setTotal(100L);
        if (pos!=null&&pos.size()>0){
            voPage.setObjects(pos.stream().map(po -> {
                StoreVO vo = new StoreVO();
                BeanUtils.copyProperties(po, vo);
                return vo;
            }).collect(Collectors.toList()));
        }
        return voPage;
    }

    private List<StorePO> getAreaStores(AreaStoreQuery areaStoreQuery,Integer businessId){
        //组织调用的sql语句 where条件拼接
        QueryWrapper<StorePO> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("business_id",businessId).and(
                wq->wq.in("province_id",areaStoreQuery.getCityIdList()).
                        or().in("city_id",areaStoreQuery.getCityIdList()).
                        or().in("area_id",areaStoreQuery.getCityIdList())
        );
        return storeRepository.list(queryWrapper);
    }

    /**
     * basic服务 管理有定位的店铺数据 可以查询的店铺的列表 附近5个
     * basic服务 通过认证解析http请求的用户身份
     */
    @Autowired
    private BasicOuputSourceManager outputSource;
    public void location(UserLocationParam param) throws BusinessException {
        //1.检查并且获取userId
        Integer userId = checkAndGetUserId();
        //2.利用定位数据获取 es中前5个店铺信息 需要es的geoDistance帮助我们完成任务
        List<StoreSearchEntity> stores=getNearStores(param);
        log.info("上报定位查询到的店铺信息:{}",stores);
        //3.封装 消息事件 发送到总线中
        MessageChannel channel = outputSource.storeLocationOutput();
        //消息体的数据 封装准备的 原则  准确 精简
        //准确 消息内容 足够使得消费者完成业务
        //精简 在保证准确的前提下 尽可能缩小消息数据的体量
        //3.1如果定位查询店铺 非空 组织消息 发送消息
        if (stores!=null&&stores.size()>0){
            for (StoreSearchEntity store : stores) {
                LocationEvent event=new LocationEvent();
                event.setUserId(userId);
                event.setStoreId(store.getId());
                channel.send(MessageBuilder.withPayload(event).build());
                log.info("成功发送用户定位店铺信息到总线:{}",event);
            }
        }
        //3.2如果定位查询店铺 为空 什么都不做
    }

    /**
     * 利用当前系统中 整合es的客户端 restHighLevelClient 组织一个搜索查询
     */
    private List<StoreSearchEntity> getNearStores(UserLocationParam param) {
        //1.搜索请求
        SearchRequest request=new SearchRequest("lbs_store");
        //2.填补 请求参数 按需求填补 只取前五条 针对lbs_store索引 搜索location 定位geoDistanceQuery
        SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
        //2.1取前五条 size 5
        sourceBuilder.size(5);
        //2.2query对象 中心点 入参param地理位置 半径20d公里
        GeoDistanceQueryBuilder query = QueryBuilders.geoDistanceQuery("location");
        query.point(Double.valueOf(param.getLatitude()), Double.valueOf(param.getLongitude()));
        query.distance(20d, DistanceUnit.KILOMETERS);
        sourceBuilder.query(query);
        request.source(sourceBuilder);
        //3.发起请求拿到响应
        List<StoreSearchEntity> entities=null;
        try{
            SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
            //response中命中的所有文档 转化成entities店铺数据
            //4.解析响应 返回 entities
            SearchHit[] docs = response.getHits().getHits();
            if (docs!=null&&docs.length>0){
                entities= Arrays.stream(docs).map(doc->{
                    return JSON.parseObject(doc.getSourceAsString(),StoreSearchEntity.class);
                }).collect(Collectors.toList());
            }
        }catch (Exception e){
            log.error("定位查询店铺异常",e);
        }
        return entities;

    }

    private Integer checkAndGetUserId() throws BusinessException {
        UserPrinciple userPrinciple = AuthenticationContextUtil.get();
        Asserts.isTrue(userPrinciple==null,new BusinessException("-2","用户认证解析失败"));
        return userPrinciple.getId();
    }
}
